package reesoft.common.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import redis.clients.jedis.Jedis;
import reesoft.common.util.ConvertUtil;
import reesoft.common.util.JedisUtil;
import reesoft.common.util.StringUtil;

import java.util.HashSet;
import java.util.Set;

/**
 * 参数配置基类
 * 基于 Redis 实现配置动态更新
 *
 * Created by reesoft on 2017-07-14
 */
public class BaseConfig implements InitializingBean
{
    private static final Logger LOG = LoggerFactory.getLogger(BaseConfig.class);

    // 配置对象集合，用于广播配置更新通知消息
    private static Set<BaseConfig> configs = new HashSet<>();

    // 订阅配置更新通知的 Redis 连接对象
    private static Jedis jedis;

    // 本节点标识
    @Value("${config.nodeId:0}")
    private long nodeId = 0;

    // 订阅配置更新通知的频道名
    @Value("${config.updateChannelName:ConfigUpdate}")
    private String updateChannelName = "ConfigUpdate";

    // 对象初始化（bean 注入属性）完成标志
    private boolean configBeanInitialized = false;

    /**
     * 构造函数
     * 所有配置对象在构造时自动加入集合
     */
    public BaseConfig()
    {
        configs.add(this);
    }

    @Override
    public void afterPropertiesSet() throws Exception
    {
        configBeanInitialized = true;
    }

    /**
     * 获取本节点标识
     *
     * @return 本节点标识
     */
    public long getNodeId()
    {
        return nodeId;
    }

    /**
     * 心跳函数，由外部驱动，每个进程内只需对一个实例进行驱动即可
     * 每次心跳检查与 Redis 服务器的连接
     */
    public void heartbeat()
    {
        if (jedis == null
                || !JedisUtil.isValid(jedis))
        {
            JedisUtil.returnResource(jedis);

            // 连接 Redis 服务器，订阅配置更新通知消息
            jedis = JedisUtil.subscribe(updateChannelName, new JedisUtil.MessageProcessor()
            {
                @Override
                public void onMessage(String channel, String message)
                {
                    LOG.info("message:" + message);

                    super.onMessage(channel, message);

                    int node = ConvertUtil.stringToInteger(StringUtil.getSubString(message, "node=", "&"), -1);
                    String configItemName = StringUtil.getSubString(message, "&name=", "&");

                    // 只有从属关系的节点才处理通知消息
                    if (node < nodeId
                            && (node & nodeId) == node
                            && !StringUtil.isBlank(configItemName))
                    {
                        for (BaseConfig config : configs)
                        {
                            config.onConfigUpdate(configItemName, StringUtil.getSubString(message, "&value=", ""));
                        }
                    }
                }
            });
        }
    }

    /**
     * 把配置更新消息发布出去
     *
     * @param configItemName 配置项名
     * @param value 配置项值
     */
    public void publishConfigUpdate(final String configItemName, final String value)
    {
        // bean 未注入完毕时不触发配置变更通知
        if (!configBeanInitialized)
        {
            return;
        }

        Jedis jedis = null;

        try
        {
            jedis = JedisUtil.getResource();

            jedis.publish(updateChannelName, "node=" + nodeId + "&name=" + configItemName + "&value=" + value);
        }
        catch (Exception ex)
        {
            LOG.error("publishConfigUpdate", ex);
        }
        finally
        {
            JedisUtil.returnResource(jedis);
        }
    }

    /**
     * 配置项更新通知，派生类重写该函数以及时更新配置项值
     *
     * @param configItemName 配置项名
     * @param value 配置项值
     */
    public void onConfigUpdate(final String configItemName, final String value)
    {
    }
}
